package com.czg.jsonschema2lombokpojo.rules;

import com.czg.jsonschema2lombokpojo.Schema;
import com.czg.jsonschema2lombokpojo.annotator.Annotator;
import com.czg.jsonschema2lombokpojo.exception.ClassAlreadyExistsException;
import com.czg.jsonschema2lombokpojo.exception.GenerationException;
import com.czg.jsonschema2lombokpojo.util.ReflectionHelper;
import com.czg.jsonschema2lombokpojo.util.SerializableHelper;
import com.fasterxml.jackson.databind.JsonNode;
import com.sun.codemodel.*;

import static com.czg.jsonschema2lombokpojo.rules.PrimitiveTypes.isPrimitive;
import static com.czg.jsonschema2lombokpojo.rules.PrimitiveTypes.primitiveType;
import static com.czg.jsonschema2lombokpojo.util.TypeUtil.resolveType;
import static org.apache.commons.lang3.StringUtils.substringAfter;
import static org.apache.commons.lang3.StringUtils.substringBefore;

/**
 * 应用“object”类型schema所需的生成步骤。
 */
public class ObjectRule implements Rule<JPackage, JType> {

    private final RuleFactory ruleFactory;
    private final ReflectionHelper reflectionHelper;


    protected ObjectRule(RuleFactory ruleFactory, ReflectionHelper reflectionHelper) {
        this.ruleFactory = ruleFactory;
        this.reflectionHelper = reflectionHelper;
    }

    /**
     * 应用此schema规则执行所需的代码生成步骤。
     * <p>
     * 当此规则应用于object类型的schema时，schema的属性用于生成新的Java类并确定其特征。详见{@link Rule}的其他实现。
     */
    @Override
    public JType apply(String nodeName, JsonNode node, JsonNode parent, JPackage _package, Schema schema) {

        JType superType = reflectionHelper.getSuperType(nodeName, node, _package, schema);
        if (superType.isPrimitive() || reflectionHelper.isFinal(superType)) {
            return superType;
        }

        JDefinedClass jclass;
        try {
            jclass = createClass(nodeName, node, _package);
        } catch (ClassAlreadyExistsException e) {
            return e.getExistingClass();
        }

        jclass._extends((JClass) superType);

        schema.setJavaTypeIfEmpty(jclass);

        if (node.has("title")) {
            ruleFactory.getTitleRule().apply(nodeName, node.get("title"), node, jclass, schema);
        }

        if (node.has("description")) {
            ruleFactory.getDescriptionRule().apply(nodeName, node.get("description"), node, jclass, schema);
        }

        if (node.has("$comment")) {
            ruleFactory.getCommentRule().apply(nodeName, node.get("$comment"), node, jclass, schema);
        }


        ruleFactory.getPropertiesRule().apply(nodeName, node.get("properties"), node, jclass, schema);

        if (node.has("javaInterfaces")) {
            addInterfaces(jclass, node.get("javaInterfaces"));
        }

        ruleFactory.getAdditionalPropertiesRule().apply(nodeName, node.get("additionalProperties"), node, jclass, schema);

        if (node.has("required")) {
            ruleFactory.getRequiredArrayRule().apply(nodeName, node.get("required"), node, jclass, schema);
        }


        return jclass;
    }

    /**
     * 创建将要生成的新Java类。
     *
     * @param nodeName 可用于指定新类名的节点名
     * @param node     表示需要新类的schema的节点。该节点可能包含一个‘javaType’属性，如果存在，该属性将覆盖新生成类的完全限定名。
     * @param _package 在此方法调用后可能包含新类的包
     * @return 对新创建的类的引用
     * @throws ClassAlreadyExistsException 如果给定的参数导致尝试创建一个已经存在的类，无论是在类路径上还是在当前要生成的类映射中。
     */
    private JDefinedClass createClass(String nodeName, JsonNode node, JPackage _package) throws ClassAlreadyExistsException {

        JDefinedClass newType;

        Annotator annotator = ruleFactory.getAnnotator();

        try {
            if (node.has("existingJavaType")) {
                String fqn = substringBefore(node.get("existingJavaType").asText(), "<");

                if (isPrimitive(fqn, _package.owner())) {
                    throw new ClassAlreadyExistsException(primitiveType(fqn, _package.owner()));
                }

                JClass existingClass = resolveType(_package, fqn + (node.get("existingJavaType").asText().contains("<") ? "<" + substringAfter(node.get("existingJavaType").asText(), "<") : ""));
                throw new ClassAlreadyExistsException(existingClass);
            }

            boolean usePolymorphicDeserialization = annotator.isPolymorphicDeserializationSupported(node);

            if (node.has("javaType")) {
                String fqn = node.path("javaType").asText();

                if (isPrimitive(fqn, _package.owner())) {
                    throw new GenerationException("javaType cannot refer to a primitive type (" + fqn + "), did you mean to use existingJavaType?");
                }

                if (fqn.contains("<")) {
                    throw new GenerationException("javaType does not support generic args (" + fqn + "), did you mean to use existingJavaType?");
                }

                int index = fqn.lastIndexOf(".") + 1;
                if (index == 0) { //Actually not a fully qualified name
                    fqn = _package.name() + "." + fqn;
                    index = fqn.lastIndexOf(".") + 1;
                }

                if (index >= 0 && index < fqn.length()) {
                    fqn = fqn.substring(0, index) + ruleFactory.getGenerationConfig().getClassNamePrefix() + fqn.substring(index) + ruleFactory.getGenerationConfig().getClassNameSuffix();
                }

                if (usePolymorphicDeserialization) {
                    newType = _package.owner()._class(JMod.PUBLIC, fqn, ClassType.CLASS);
                } else {
                    newType = _package.owner()._class(fqn);
                }
                ruleFactory.getLogger().debug("Adding " + newType.fullName());
            } else {
                final String className = ruleFactory.getNameHelper().getUniqueClassName(nodeName, node, _package);
                if (usePolymorphicDeserialization) {
                    newType = _package._class(JMod.PUBLIC, className, ClassType.CLASS);
                } else {
                    newType = _package._class(className);
                }
                ruleFactory.getLogger().debug("Adding " + newType.fullName());
            }
        } catch (JClassAlreadyExistsException e) {
            throw new ClassAlreadyExistsException(e.getExistingClass());
        }

        if (ruleFactory.getGenerationConfig().isSerializable()) {
            SerializableHelper.addSerializableSupport(newType);
        }
        annotator.typeInfo(newType, node,false);
        annotator.propertyInclusion(newType, node);

        return newType;

    }

    private void addInterfaces(JDefinedClass jclass, JsonNode javaInterfaces) {
        for (JsonNode i : javaInterfaces) {
            jclass._implements(resolveType(jclass._package(), i.asText()));
        }
    }

}
